package org.xgluo.excle;

import com.sun.org.slf4j.internal.Logger;
import com.sun.org.slf4j.internal.LoggerFactory;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.w3c.dom.*;
import org.xgluo.Constant;
import org.xgluo.config.XmlConfiguration;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author LXG
 * @program: JBOToExcle
 * @name: WriteExcle
 * @description: 将内容写入Excle文件
 * @date 2024年12月03日
 */
public class WriteExcle {

    //定义一个文件写入对象Workbook
    private Workbook wb;

    private String desFileUrl;

    private List<String> nodeAttributeList;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /*
     * @Description: 构造函数写入前准备目标文件和xml元数据对象：构造函数根据传入的待处理文件路径，生成目标文件，并获取Excle文件写入对象Workbook
     * @param url
     * @date 2024/11/28 10:15
     * @author LXG
     */
    public WriteExcle(String url) {
        this(url, ExcleConstant.EXCLE_TYPE_XLSX);
    }

    /**
     * @Description: 构造函数写入前准备目标文件和xml元数据对象：构造函数根据传入的待处理文件路径，生成目标文件，并获取Excle文件写入对象Workbook
     * @param url 待解析的文件路径
     * @date 2024/11/28 10:15
     * @author LXG
     */
    public WriteExcle(String url, String typeExcle) {
        //Excle文件类型,默认xlsx
        try {
            if (url == null || url.isEmpty()) {
                return;
            }
            //为原文件名加个后缀名，再打开文件
            desFileUrl = url.substring(0, url.lastIndexOf(".")) + "_Restul." + typeExcle;
            //根据给定的文件名，复制出来一个新的Xlsx文件
            Files.copy(Objects.requireNonNull(WriteExcle.class.getClassLoader().getResourceAsStream(Constant.DEST_XLSX_FILE_PATH)), Paths.get(desFileUrl), StandardCopyOption.REPLACE_EXISTING);
            wb = ExcleUtil.getWorkBookByType(desFileUrl);
        } catch (IOException e) {
            logger.error("打开文件失败，文件路径错误，或文件格式错误！");
            e.printStackTrace();
        }
    }

    /**
     * @description: 解析Document对象写入Excle
     * @param document          待解析对象
     * @date 2024/12/3
     * @author LXG
     */
    public void writeSheet(Document document) {
        NodeList nodeList = null;
        if(ExcleConstant.SHEET_NAME_TYPE_REPEAT.equals(XmlConfiguration.getSheetNodeType())){
            // 没有重复节点，所有节点都解析成一个Sheet
            nodeList = parseRepeatNode(document);
        }else if(ExcleConstant.SHEET_NAME_TYPE_ALL.equals(XmlConfiguration.getSheetNodeType())){
            // 有重复节点，解析指定的节点，将指定的节点，分别解析
            nodeList = parseAllNode(document);
        }
        if(nodeList == null){
            System.out.println(">>>>>>>>>>>>>>>未能获取到符合要求的sheet节点列表");
        }else {
            parseNodeList(nodeList);
        }
    }


    /**
     * @description: 解析重复类节点：读取需要解析的Sheet节点列表
     * @param document 待解析对象
     * @date 2024/12/7
     * @author LXG
     */
    public NodeList parseRepeatNode(Document document) {
        NodeList sheetNodes;
        if (XmlConfiguration.getSheetNodeName() == null || XmlConfiguration.getSheetNodeName().isEmpty()) {
            logger.error(">>>>>>>>>>>配制解析重复节点时，需要同时配制重复节点的节点名称！");
            sheetNodes = document.getChildNodes();
        } else {
            sheetNodes = document.getElementsByTagName(XmlConfiguration.getSheetNodeName());
        }
        return sheetNodes;
    }

    /**
     * @description: 读取节点的所有子节点名称作为sheet名称
     * @param document 待解析对象
     * @date 2024/12/7
     * @author LXG
     */
    public NodeList parseAllNode(Document document) {
        return document.getChildNodes();
    }


    /**
     * @description: 开始遍历解析Sheet节点列表
     * @param sheetNodes 待解析的Sheet节点列表
     * @date 2025/3/16
     * @author xgluo
     */
    public void parseNodeList (NodeList sheetNodes) {
        Element item;
        String sheetName;
        String sheetDescription;
        Sheet sheet;
        // 声明一个目录Sheet页
        Sheet contents = wb.getSheet(ExcleConstant.DIRECTORY_SHEET_NAME);
        if(contents == null){
            contents = wb.createSheet(ExcleConstant.DIRECTORY_SHEET_NAME);
        }
        // 声明一个目录页的行和列
        Row row;
        Cell cell;
        // 遍历所有的节点，每个节点是一个Sheet
        for (int i = 0; i < sheetNodes.getLength(); i++) {
            item = (Element) sheetNodes.item(i);
            sheetName = item.getAttribute(XmlConfiguration.getSheetNameAttribute());
            sheetDescription = item.getAttribute(XmlConfiguration.getSheetDescriptionAttribute());
            sheet = wb.getSheet(sheetName);
            if (sheet == null) {
                sheet = wb.createSheet(sheetName);
            }
            CellStyle cellStyle = setWbType(wb);
            // 写入单个sheet页
            horizontalPrint(sheet,cellStyle, item);
            // 写入目录页，将每个sheet页的简单信息写入目录页中
            row = contents.createRow(i);
            // 写入目录页sheet名称
            cell = row.createCell(0);
            cell.setCellValue(sheetName);
            // 写入目录页sheet描述
            cell = row.createCell(1);
            cell.setCellValue(sheetDescription);
        }

    }


    /***
     * @description: 设置单元格的格式
     * @param wb Excle待写入的对象
     * @return CellStyle
     * @date 2025/3/16
     * @author xgluo
     */
    public static CellStyle setWbType(Workbook wb) {
        CellStyle cellstyle = wb.createCellStyle();
        cellstyle.setAlignment(HorizontalAlignment.CENTER);
        XSSFFont hf = (XSSFFont) wb.createFont();
        hf.setBold(false);
        cellstyle.setFont(hf);
        cellstyle.setBorderLeft(BorderStyle.THIN);
        cellstyle.setBorderRight(BorderStyle.THIN);
        cellstyle.setBorderBottom(BorderStyle.THIN);
        cellstyle.setBorderTop(BorderStyle.THIN);
        return cellstyle;
    }

    /**
     * @description: 遍历节点，横向写入Sheet，即先写入行
     * @param sheet 待写入的Sheet页对象
     * @param style 写入单元格格式
     * @param element 待解析的Element对象
     * @date 2024/12/5
     * @author LXG
     */
    public void horizontalPrint(Sheet sheet, CellStyle style, Element element){

        //每个sheet页重新定义列名数组
        nodeAttributeList = new ArrayList<>();
        Cell cell;
        Row row;
        NodeList rowNodes = null;
        int rowLength;

        if(ExcleConstant.ROW_NODE_TYPE_ATTRIBUTE.equals(XmlConfiguration.getRowNodeType())){
            rowNodes = element.getElementsByTagName(XmlConfiguration.getRowNodeName());
            rowLength = rowNodes.getLength();
            for (int i = 0; i < rowLength; i++) {
                row = sheet.createRow(XmlConfiguration.getRowOffset()+i);
                writeRowByAttibure((Element) rowNodes.item(i), row, style);
            }
        }else if(ExcleConstant.ROW_NODE_TYPE_CHILD.equals(XmlConfiguration.getRowNodeType())){
            rowNodes = element.getChildNodes();
            rowLength = rowNodes.getLength();
            for (int i = 0; i < rowLength; i++) {
                row = sheet.createRow(XmlConfiguration.getRowOffset()+i);
                writeRowByNode((Element) rowNodes.item(i), row, style);
            }
        }
        if(rowNodes == null ){
            logger.error("无法识别的行内容节点类型");
            throw new RuntimeException("无法识别的行内容节点类型");
        }

        //写入字段标题行内容
        int index = XmlConfiguration.getRowOffset();
        // 行偏移大于0，才写入标题行
        if(index>0){
            row = sheet.createRow(index-1);
            int j = 0;
            for (String nodeName : nodeAttributeList) {
                //sheet.setColumnWidth(offsetCell+j, 9000);
                cell = row.createCell(XmlConfiguration.getCellOffset() + j);
                cell.setCellValue(XmlConfiguration.getAttributeNameMap().getOrDefault(nodeName, nodeName));
                cell.setCellStyle(style);
                j++;
            }
        }
        // 行偏移大于1时才写入sheet页描述内容
        if(index>1){
            //写入sheet页描述内容
            row = sheet.createRow(0);
            cell = row.createCell(0);
            // 定义列偏移量
            int cellIndex = 1;
            cell.setCellValue(sheet.getSheetName());
            cell.setCellStyle(style);
            NamedNodeMap attributes = element.getAttributes();
            int sheetAttributeLength = attributes.getLength();
            for (int t=0 ;t<sheetAttributeLength; ) {
                Node sheetAttribute = attributes.item(t);
                if(!XmlConfiguration.getSheetNameAttribute().equals(sheetAttribute.getNodeName())){
                    cell = row.createCell(cellIndex++);
                    cell.setCellValue(sheetAttribute.getNodeValue());
                    cell.setCellStyle(style);
                }
                t++;
            }
        }
    }


    /**
     * @description: 遍历节点，纵向写入Excle，即先写入列 ：待扩展
     * @param sheet 待写入的Sheet页对象
     * @param style 写入单元格格式
     * @param element 待解析的Element对象
     * @date 2024/12/5
     * @author LXG
     */
    public void verticalPrint(Sheet sheet, CellStyle style, Element element) {

    }



    /**
     * @description: 所有直系子节点作为一行，每个子节点的直系子节点作为一个单元格
     * @param element 要解析的element对象
     * @param style 单元格格式
     * @param row 要写入的Row行对象
     * @date 2024/12/11
     * @author LXG
     */
    private void writeRowByNode(Element element,Row row, CellStyle style){
        //解析对象写入行数据前，如果需要对数据处理,在方法中处理
        cellValueDeal(element);

        NodeList cellNodes = element.getChildNodes();
        String cellNodeName;
        Cell cell;
        Node cellNode;
        Node cellTextNode;
        int indexNodeName;
        int attributeLength = cellNodes.getLength();
        for (int i = 0; i < attributeLength; i++) {
            cellNode = cellNodes.item(i);
            cellNodeName = cellNode.getNodeName();
            if(!nodeAttributeList.contains(cellNodeName)){
                nodeAttributeList.add(cellNodeName);
            }
            cellTextNode = cellNode.getFirstChild();
            if(Node.TEXT_NODE == cellTextNode.getNodeType()){
                indexNodeName = nodeAttributeList.indexOf(cellNodeName);
                cell = row.createCell(indexNodeName + XmlConfiguration.getCellOffset());
                cell.setCellStyle(style);
                cell.setCellValue(cellTextNode.getNodeValue());
            }else{
                logger.debug("节点："+element.getNodeName()+"下的子节点："+cellNodeName+" 不是叶子节点，或节点内内容为空");
            }
        }
    }

    /**
     * @description: 每个子节点作为一行，子节点的属性作为单元格内容锁
     * @param element 要解析的element对象
     * @param style 单元格格式
     * @param row 要写入的Row行对象
     * @date 2024/12/5
     * @author LXG
     */
    public void writeRowByAttibure(Element element,Row row, CellStyle style){

        //解析对象写入行数据前，如果需要对数据处理,在方法中处理
        cellValueDeal(element);

        NamedNodeMap attributes = element.getAttributes();
        String attributeName;
        String attributeValue;
        Cell cell;
        int attributeLength = attributes.getLength();
        for (int i = 0; i < attributeLength; i++) {
            attributeName = attributes.item(i).getNodeName();
            if(!nodeAttributeList.contains(attributeName)){
                nodeAttributeList.add(attributeName);
            }
            attributeValue = attributes.item(i).getNodeValue();
            int indexAttribute = nodeAttributeList.indexOf(attributeName);
            cell = row.createCell(indexAttribute + XmlConfiguration.getCellOffset());
            cell.setCellStyle(style);
            cell.setCellValue(attributeValue);
        }
    }

    /**
     * @description: 将工作簿内容写入文件
     * @date 2024/12/5
     * @author LXG
     */
    public void writeWorkbookToFile() {
        FileOutputStream fos = null;
        try{
            fos = new FileOutputStream(this.desFileUrl);
            wb.write(fos);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if(fos != null ){
                try{
                    fos.close();
                } catch (IOException e) {
                    logger.error(e.getMessage());
                }
            }
        }
    }

    /**
     * @description: 如果需要对数据进行额外处理，在这里处理
     * @param element 待处理对象
     * @date 2024/12/5
     * @author LXG
     */
    public void cellValueDeal(Element element){

        NamedNodeMap attributes = element.getAttributes();
        Node label = attributes.getNamedItem("type");
        if(label != null){
            String labelAttributeValue = label.getNodeValue();
            if(Constant.COLUMN_TYPE_STRING.equalsIgnoreCase(labelAttributeValue)){
                label.setNodeValue("VARCHAR");
            }
        }
    }


}
